package org.ccay.core.orm.plugins;



import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.ccay.core.base.PageVO;
import org.ccay.core.base.PagedResult;
import org.ccay.core.orm.DBType;
import org.ccay.core.orm.ExtendedBoundSql;



/**
 * 分页插件
 * @author chaowangbang
 * @since 2016年8月6日
 */
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})})
public class PageInterceptor implements Interceptor {
    private static final Log logger = LogFactory.getLog(PageInterceptor.class);
    private static final int INEX_MAPPED_STATEMENT=0;
    private static final int INEX_PARAMETER=1;
    private static final int INEX_ROW_BOUNDS=2;
    private static final int INEX_RESULT_HANDLER=3;
    
	/**取结果集和记录总数 */
	private static final int RESULT_MODE_ALL = 0;
	/**只取记录总数 */
	private static final int RESULT_MODE_TOTALROWS = 1;
	/**只取结果集 */
	private static final int RESULT_MODE_DATASET = 2;
    
    private static final String ORACEL_PAGE_PREFIX = "SELECT * FROM (SELECT INNER__T.*, ROWNUM AS ROWNO FROM ( ";
    
    private static final String ORACEL_PAGE_SUFFIX = " ) INNER__T WHERE ROWNUM <= ? ) OUTER__T WHERE OUTER__T.ROWNO >= ? ";
    
    private static final String MYSQL_PAGE_SUFFIX = " LIMIT ? , ? ";
    
    /**
     * SQL id 以 Count结尾的被认为是查询总数量的SQL
     */
    private static final String COUNT_SQL_POST_FIX="Count";
    
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		Object[] queryArgs = invocation.getArgs();
		MappedStatement mappedStatement = (MappedStatement) queryArgs[INEX_MAPPED_STATEMENT];
		String sqlId = mappedStatement.getId();
		
		Object parameter = queryArgs[INEX_PARAMETER];
		Map.Entry<String,Object> pageEntry = checkInvocation(sqlId,parameter);
		if(null == pageEntry){
			return invocation.proceed();
		}
		PageVO page = (PageVO) pageEntry.getValue();
		String pageParamsName = pageEntry.getKey();
		
		Executor executor = (Executor)invocation.getTarget();
		//如果总数为0 则尝试通过SQL查询总数
		if (((page.getResultMode() == RESULT_MODE_ALL) && (page.getTotalRows() == 0))
				|| (page.getResultMode() == RESULT_MODE_TOTALROWS)) {
			queryCount(queryArgs, mappedStatement, sqlId, page, executor);
		}
		List resultList = null;
		//如果总数大于0，才执行获取内容的SQL查询 
		if (((page.getResultMode() == RESULT_MODE_ALL) && (page.getTotalRows() > 0))
				|| (page.getResultMode() == RESULT_MODE_DATASET)) {
			// 查询结果集处理
			resultList = queryResultList(executor, mappedStatement, queryArgs, page, pageParamsName);
			// 异步只取结果集时修正记录总数
			if (resultList.size() > page.getTotalRows()) {
				page.setTotalRows(resultList.size());
			}
		} else {
			//resultList = new ArrayList();
			//若未使用查询Count sql 则使用list sql的数据条数 hwx210974
			if(page.getTotalRows()==0){
				resultList = queryResultList(executor, mappedStatement, queryArgs, page, pageParamsName);
				page.setTotalRows(resultList.size());
			}
		}
		
		PagedResult pagedResult = new PagedResult();
		pagedResult.setPageVO(page);
		pagedResult.setResult(resultList);
		
		List<PagedResult> returnResultList = new ArrayList<PagedResult>();
		
		returnResultList.add(pagedResult);
		return returnResultList;
	}

	@SuppressWarnings("rawtypes")
	private List queryResultList(Executor executor,
			MappedStatement mappedStatement, Object[] queryArgs,PageVO page,String pageParamsName) throws SQLException{
		Object parameter = queryArgs[INEX_PARAMETER];
		RowBounds rowBounds = (RowBounds) queryArgs[INEX_ROW_BOUNDS];
		ResultHandler resultHandler = (ResultHandler) queryArgs[INEX_RESULT_HANDLER];
		Configuration configuration = mappedStatement.getConfiguration();
		BoundSql boundSql = mappedStatement.getBoundSql(parameter);
		String originalSql = boundSql.getSql().trim();  
		CacheKey pageKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, boundSql);
		if(null != page){
			//获取分页语句
			if(configuration.getDatabaseId().equals(DBType.ORACLE)){
				//oracel分页rownum从1开始
				page.setIndexBaseZero(false);
			}
			String pagedSql = getPageVOResultList(originalSql,configuration.getDatabaseId(),pageParamsName);
			if(null != pagedSql){
				//设置分页SQL
				setPagedSql(boundSql,pagedSql);
				//设置分页参数
				addParameterMapping(configuration,boundSql,pageParamsName);
			}
		}
		return executor.query(mappedStatement, parameter, rowBounds, resultHandler, pageKey, boundSql);
	}
	
	private void setPagedSql(BoundSql boundSql,String pagedSql){
		if(boundSql instanceof ExtendedBoundSql){
			ExtendedBoundSql extendedBoundSql = (ExtendedBoundSql)boundSql;
			extendedBoundSql.setFinalSql(pagedSql);
		}else{
			try {
				// 不让Java语言检查访问修饰符
				Field sqlField = BoundSql.class.getDeclaredField("sql");
				sqlField.setAccessible(true);
				sqlField.set(boundSql, pagedSql);
			} catch (NoSuchFieldException e) {
				e.printStackTrace();
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		
		try {
			List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings() ;
			//Collections.UnmodifiableRandomAccessList cannot be modify 
			if(parameterMappingList.getClass().getSimpleName().equals("UnmodifiableRandomAccessList")){
				parameterMappingList = new ArrayList<ParameterMapping>(parameterMappingList);
				Field parameterMappingsField = BoundSql.class.getDeclaredField("parameterMappings");
				parameterMappingsField.setAccessible(true);
				parameterMappingsField.set(boundSql, parameterMappingList);
			}
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}
	
	private String getPageVOResultList(String originalSql,String databaseId,String pageParamsName){
		if(databaseId.equals(DBType.MYSQL)){
			return originalSql + MYSQL_PAGE_SUFFIX;
		}else if(databaseId.equals(DBType.ORACLE)){
			return ORACEL_PAGE_PREFIX+originalSql+ORACEL_PAGE_SUFFIX;
		}
		logger.warn("分页插件目前还不支持当前数据库产品"+databaseId+"请在确保已经在Mapping.xml中正确处理分页逻辑！");
		return originalSql;
	}
	
	private void addParameterMapping(Configuration configuration,BoundSql boundSql,String pageParamsName){
		String databaseId = configuration.getDatabaseId();
		List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings() ;
		if(databaseId.equals(DBType.MYSQL)){
			ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, pageParamsName+".startIndex", Integer.class);
			parameterMappingList.add(builder.build());
			builder = new ParameterMapping.Builder(configuration, pageParamsName+".endIndex", Integer.class);
			parameterMappingList.add(builder.build());
		}
		if(databaseId.equals(DBType.ORACLE)){
			ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, pageParamsName+".endIndex", Integer.class);
			parameterMappingList.add(builder.build());
			builder = new ParameterMapping.Builder(configuration, pageParamsName+".startIndex", Integer.class);
			parameterMappingList.add(builder.build());
		}
	}

	@SuppressWarnings("rawtypes")
	private void queryCount(Object[] queryArgs, MappedStatement mappedStatement, String sqlId, PageVO page, Executor executor) throws SQLException {
		Object parameter = queryArgs[INEX_PARAMETER];
		RowBounds rowBounds = (RowBounds) queryArgs[INEX_ROW_BOUNDS];
		ResultHandler resultHandler = (ResultHandler) queryArgs[INEX_RESULT_HANDLER];
		String queryCountSqlId = sqlId + COUNT_SQL_POST_FIX;
		Configuration configuration = mappedStatement.getConfiguration();
		MappedStatement queryCountMs = configuration.getMappedStatement(queryCountSqlId);
		
		//处理自定义标签
		CdataTagInterceptor pi = new CdataTagInterceptor();
		MappedStatement newStatement = pi.buildMappedStatement(queryCountMs);
		
		List queryCountList = executor.query(newStatement, parameter, rowBounds, resultHandler);
		int totalRows = Integer.parseInt(queryCountList.get(0).toString());
		page.setTotalRows(totalRows);
	}

	private Map.Entry<String,Object> checkInvocation(String sqlId, Object parameter) {
		if(null == parameter){
			return null;
		}
		if((null != sqlId)&& !(sqlId.endsWith(COUNT_SQL_POST_FIX))){
			return findPageVO(parameter);
		}
		return null;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private Entry<String, Object> findPageVO(Object parameter) {
		if(null == parameter){
			return null;
		}
		if(parameter instanceof Map){
			Map<String, Object> paramenterMap = (Map)parameter;
			for(Map.Entry<String,Object> entry:paramenterMap.entrySet()){
				if(entry.getValue() instanceof PageVO){
					return entry;
				}
			}
		}
		return null;
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
		
	}
}